package com.px.lovepath.ui.general.pic;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;

import com.px.lovepath.utils.cache.BitmapCache;
import com.px.lovepath.utils.cache.Cache;
import com.px.lovepath.utils.session.WebService;
import com.px.lovepath.utils.session.WebSession;
import com.px.lovepath.utils.session.WebSession.CacheStrategy;
import com.px.lovepath.utils.session.WebSessionException;

public class PicView extends FrameLayout {
	private final Context mContext;
	private String mPicUri = null;
	private String mUserAgent = null;
	private int mPicBackgroundRes = 0;
	private Drawable mPicBackgroundDrawable = null;
	private PicStretch mPicStretch = PicStretch.SCALE_INSIDE;
	private WebSession mPicSession = null;
	private PicViewListener mPicListener = null;
	private PicViewDecoder mPicDecoder = null;
	private boolean mHasPicError = false;
	private File mSavePicAs = null;
	private boolean mIsPicSaved = false;
	private int mPicErrorRes = 0;
	private View mPicErrorView = null;
	private int mPicLoadingRes = 0;
	private View mPicLoadingView = null;
	private CompressFormat mCompressFormat = CompressFormat.PNG;
	private int mCompressQuality = 100;
	private static BitmapCache mPicCache = new BitmapCache("pic", 50);
	private static int mVisiblePicCount = 0;
	private boolean mIsPicVisible = false;
	private boolean mIsRoundCorner = false;
	private int mRoundCornerRadius = 20 ;
	
	// ### 构造函数 ###
	public PicView(Context context) {
		this(context, null);
	}
	public PicView(Context context, AttributeSet attrs) {
		super(context, attrs);
		
		mContext = context;
		setWillNotDraw(false);
		setPicVisibleState(getVisibility() == VISIBLE);
	}
	
	// ### 属性 ###
	public String getPicUri() {
		return mPicUri;
	}
	public void setPicUri(String picUri) {
		if (mPicUri != picUri) {
			closePicSession();

			mPicUri = picUri;
			mIsPicSaved = false;
			mPicBackgroundDrawable = null;
			mHasPicError = false;
			removePicErrorView();
			removePicLoadingView();
			requestLayout();
		}
	}
	public void setUserAgent(String userAgent) {
		mUserAgent = userAgent;
	}
	public void setPicBackgroundResource(int picRes) {
		if (mPicBackgroundRes != picRes) {
			mPicBackgroundRes = picRes;
			mPicBackgroundDrawable = null;
			invalidate();
		}
	}
	public Drawable getPicForegroundDrawable() {
		return mPicBackgroundDrawable;
	}
	public void setPicStretch(PicStretch picStretch) {
		if (mPicStretch != picStretch) {
			mPicStretch = picStretch;
			
			invalidate();
		}
	}
	public void setPicListener(PicViewListener picListener) {
		mPicListener = picListener;
	}
	public void setPicDecoder(PicViewDecoder picDecoder) {
		mPicDecoder = picDecoder;
	}
	public void setSavePicAs(File saveAsFile) {
		mSavePicAs = saveAsFile;
	}
	public void setPicErrorResource(int errorRes) {
		if (mPicErrorRes != errorRes) {
			mPicErrorRes = errorRes;

			removePicErrorView();
			invalidate();
		}
	}
	public void setPicLoadingResource(int loadingRes) {
		if (mPicLoadingRes != loadingRes) {
			mPicLoadingRes = loadingRes;

			removePicLoadingView();
			invalidate();
		}
	}
	public void setCompressFormat(CompressFormat compressFormat) {
		mCompressFormat = compressFormat;
	}
	public void setCompressQuality(int compressQuality) {
		mCompressQuality = compressQuality;
	}
	public static BitmapCache getPicCache() {
		return mPicCache;
	}
	public void setIsRoundCorner(boolean isRoundCorner, int radius) {
		mIsRoundCorner = isRoundCorner;
		mRoundCornerRadius = radius;
	}
	
	// ### View函数重写 ###
	@Override
	protected void onDraw(Canvas canvas) {
		// 计算图片尺寸
		Rect dstRect = new Rect();
		dstRect.left = getPaddingLeft();
		dstRect.top = getPaddingTop();
		dstRect.right = getWidth() - getPaddingRight();
		dstRect.bottom = getHeight() - getPaddingBottom();
		canvas.clipRect(dstRect);
		
		if (mPicBackgroundRes != 0 && mPicBackgroundDrawable == null) {
			mPicBackgroundDrawable = mContext.getResources().getDrawable(mPicBackgroundRes);
		}

		if (mPicBackgroundDrawable != null) {
			mPicBackgroundDrawable.setBounds(dstRect);
			mPicBackgroundDrawable.draw(canvas);
		}
		
		while (mHasPicError == false && mPicUri != null){
			if (mPicCache.containsSlotInMemory(new BitmapCache.CacheKey(mPicUri))) {
				Cache.CacheSlot<Bitmap> cacheSlot = mPicCache.aquireCachedSlot(new BitmapCache.CacheKey(mPicUri));
				if (cacheSlot != null) {
					drawPic(canvas, dstRect, cacheSlot.getValue());
					mPicCache.releaseSlot(cacheSlot);
				
					if (mIsPicSaved == false && mSavePicAs != null)
						openPicSession();
					break;
				}
			}
			openPicSession();
			break;
		}
		
		super.onDraw(canvas);
	}
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		if (mHasPicError) {
			removePicLoadingView();
			addPicErrorView();
		} else {
			if (mPicUri != null) {
				if (mPicCache.containsSlotInMemory(new BitmapCache.CacheKey(mPicUri)) == false) {
					removePicErrorView();
					addPicLoadingView();
				} else {
					removePicErrorView();
					removePicLoadingView();
				}
			}

		}
		
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}
	@Override
	protected void onVisibilityChanged(View changedView, int visibility) {
		super.onVisibilityChanged(changedView, visibility);

		boolean isPicVisible = visibility == VISIBLE;
		setPicVisibleState(isPicVisible);
		if (isPicVisible == false) {
			closePicSession();
			shrinkPicCache();
		}
	}
	@Override
	protected void onAttachedToWindow() {
		super.onAttachedToWindow();

		setPicVisibleState(getVisibility() == VISIBLE);
	}
	@Override
	protected void onDetachedFromWindow() {
		super.onDetachedFromWindow();

		closePicSession();
		setPicVisibleState(false);
		shrinkPicCache();
	}
	
	// ### 实现函数 ###
	private void notifyPicError() {
		if (mPicListener != null) {
			mPicListener.onPicError(this);
		}
	}
	private void drawPic(Canvas canvas, Rect dstRect, Bitmap picBitmap) {
		if (mIsRoundCorner) {
			picBitmap = toRoundCorner(picBitmap);
		}
		final Pair<Float, Float> ratios = calcStretchRatios(dstRect.width(), dstRect.height(), picBitmap.getWidth(), picBitmap.getHeight());
		final int finalWidth = Math.round(picBitmap.getWidth() * ratios.first);
		final int finalHeight = Math.round(picBitmap.getHeight() * ratios.second);
		final int finalX;
		final int finalY;
		
		switch (mPicStretch) {
		case CENTER:
		case SCALE_CROP:
		case SCALE_INSIDE:
		case SCALE_FILL:
			finalX = dstRect.left + (dstRect.width() - finalWidth) / 2;
			finalY = dstRect.top + (dstRect.height() - finalHeight) / 2;
			break;
		default:
			finalX = 0;
			finalY = 0;
			assert false;
		}
		
		Paint paint = new Paint();
		paint.setFilterBitmap(true);
		paint.setAntiAlias(true);
		canvas.clipRect(dstRect);
		canvas.drawBitmap(picBitmap, 
				new Rect(0, 0, picBitmap.getWidth(), picBitmap.getHeight()), 
				new Rect(finalX, finalY, finalX + finalWidth, finalY + finalHeight), 
				paint
		);		
	}
	private Bitmap toRoundCorner(Bitmap bitmap) {                    
    	Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);          
    	Canvas canvas = new Canvas(output);            
    	final int color = 0xff424242;          
    	final Paint paint = new Paint();         
    	final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());         
    	final RectF rectF = new RectF(rect);         
    	paint.setAntiAlias(true);         
    	canvas.drawARGB(0, 0, 0, 0);         
    	paint.setColor(color);          
    	canvas.drawRoundRect(rectF, mRoundCornerRadius, mRoundCornerRadius, paint);            
    	paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));          
    	canvas.drawBitmap(bitmap, rect, rect, paint);           
    	return output;      
    }
	private Pair<Float, Float> calcStretchRatios(int dstWidth, int dstHeight, int srcWidth, int srcHeight) {
		switch (mPicStretch) {
		case CENTER:
			return new Pair<Float, Float>(1.0f, 1.0f);
		case SCALE_CROP:
			float maxRatio = Math.max((float) dstWidth / srcWidth, (float) dstHeight / srcHeight);
			return new Pair<Float, Float>(maxRatio, maxRatio);
		case SCALE_INSIDE:
			float minRatio = Math.min((float) dstWidth / srcWidth, (float) dstHeight / srcHeight);
			return new Pair<Float, Float>(minRatio, minRatio);
		case SCALE_FILL:
			return new Pair<Float, Float>((float) dstWidth / srcWidth, (float) dstHeight / srcHeight);
		default:
			assert false;
			return new Pair<Float, Float>(1.0f, 1.0f);
		}
	}
	private void addPicLoadingView() {
		if (mPicLoadingRes != 0 && mPicLoadingView == null) {
			mPicLoadingView = LayoutInflater.from(mContext).inflate(mPicLoadingRes, null);
			addView(mPicLoadingView);
		}
	}
	private void removePicLoadingView() {
		if (mPicLoadingView != null) {
			removeView(mPicLoadingView);
		}
	}
	private void addPicErrorView() {
		if (mPicErrorRes != 0 && mPicErrorView == null) {
			mPicErrorView = LayoutInflater.from(mContext).inflate(mPicErrorRes, null);
			addView(mPicErrorView);
		}
	}
	private void removePicErrorView() {
		if (mPicErrorView != null) {
			removeView(mPicErrorView);
		}
	}
	private boolean isLocalPic(Uri picUri) {
		return picUri.getScheme().equalsIgnoreCase("file");
	}
	private void openPicSession() {
		if (mPicSession != null && mPicSession.getSessionState() == WebSession.SessionState.UNFINISHED)
			return;
		
		mPicSession = new PicViewSession() {
			private String mSessionPicUri = null;
			private PicViewDecoder mSessionPicDecoder = null;
			private File mSessionSavePicAs = null;
			private CompressFormat mSessionCompressFormat = CompressFormat.PNG;
			private int mSessionCompressQuality = 100;
			private boolean mSessionIsPicSaved = false;
			
			@Override 
			protected void onSessionOpen() {
				// 复制会话参数, 保证多线程下参数不受干扰.
				mSessionPicUri = mPicUri;
				mSessionPicDecoder = mPicDecoder;
				mSessionSavePicAs = mSavePicAs;
				mSessionIsPicSaved = mIsPicSaved;
				mSessionCompressFormat = mCompressFormat;
				mSessionCompressQuality = mCompressQuality;
			}
			@Override
			protected void onSessionTry() throws Exception {
				WebService picService = new WebService(this);

				Bitmap picBitmap = null;
				Cache.CacheSlot<Bitmap> cacheSlot = mPicCache.aquireCachedSlot(new BitmapCache.CacheKey(mSessionPicUri));
					
				// 获取图片位图
				if (cacheSlot == null) {
					Uri picUri = Uri.parse(mSessionPicUri);
					if (isLocalPic(picUri)) {
						if (mSessionPicDecoder != null) {
							picBitmap = mSessionPicDecoder.decodePic(mSessionPicUri, null);
						}
						
						if (picBitmap == null) {
							picBitmap = BitmapFactory.decodeFile(picUri.getPath());
						}
					} else {
						if (mSessionPicDecoder != null) {
							HttpGet httpRequest = new HttpGet(mSessionPicUri);
							HttpResponse httpResponse = execute(httpRequest);
							HttpEntity entity = httpResponse.getEntity();

							InputStream picStream = entity.getContent();
							picBitmap = mSessionPicDecoder.decodePic(mSessionPicUri, picStream);
							picStream.close();
							entity.consumeContent();
						}
						
						if (picBitmap == null)
							picBitmap = picService.getBitmapContent(mSessionPicUri);
					}

					// 缓存图片
					if (picBitmap != null)
						cacheSlot = mPicCache.aquireNewSlot(new BitmapCache.CacheKey(mSessionPicUri, mSessionCompressFormat, mSessionCompressQuality), picBitmap);
						
				} else {
						picBitmap = cacheSlot.getValue();
				}
					
				// 保存图片位图
				if (mSessionSavePicAs != null && mSessionIsPicSaved == false && picBitmap != null) {
					mSessionIsPicSaved = saveBitmapAs(mSessionSavePicAs, picBitmap, mSessionCompressFormat, mSessionCompressQuality);
				}

				if (cacheSlot != null)
					mPicCache.releaseSlot(cacheSlot);

				// 检查图片是否获取成功
				if (picBitmap == null) {
					throw new WebSessionException();
				}
			}
			@Override
			protected void onSessionSucceeded() {
				if (mPicSession == this) {
					invalidate();
				}
			}
			@Override
			protected void onSessionFailed() {
				if (mPicSession == this) {
					mHasPicError = true;
					notifyPicError();
					requestLayout();
					invalidate();
				}
			}
			@Override
			protected void onSessionClosed() {
				if (mPicSession == this) {
					mPicSession = null;
					mIsPicSaved = mSessionIsPicSaved;
				}
				shrinkPicCache();
			}
		};
		
		mPicSession.setDefaultUserAgent(mUserAgent);
		mPicSession.open(CacheStrategy.USE_CACHE_IF_EXISTS);
		
		if (mPicLoadingRes != 0 && mPicLoadingView == null)
			requestLayout();
	}
	private void closePicSession() {
		if (mPicSession != null) {
			mPicSession.close();
			mPicSession = null;
		}
	}
	private boolean saveBitmapAs(File saveAsFile, Bitmap bitmap, CompressFormat compressFormat, int compressQuality) {
		if (saveAsFile != null) {
			FileOutputStream fileStream = null;
			try {
				fileStream = new FileOutputStream(saveAsFile);
				bitmap.compress(compressFormat, compressQuality, fileStream);
			} catch (Exception e) {
				e.printStackTrace();
				return false;
			} finally {
				try {
					fileStream.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			return true;
		}
		return false;
	}
	private void shrinkPicCache() {
		if (mPicCache.getUsedSlotCount() > mVisiblePicCount * 1.5) {
			mPicCache.shrinkSlot(mVisiblePicCount);
		}
	}
	private void setPicVisibleState(boolean isPicVisible) {
		if (mIsPicVisible != isPicVisible) {
			mIsPicVisible = isPicVisible;
			
			if (mIsPicVisible)
				mVisiblePicCount++;
			else
				mVisiblePicCount--;
		}
	}
}
